package phases

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/pkg/errors"
	bkeaddon "gopkg.openfuyao.cn/bkecommon/cluster/addon"
	confv1beta1 "gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	bkeinit "gopkg.openfuyao.cn/bkecommon/cluster/initialize"
	bkenode "gopkg.openfuyao.cn/bkecommon/cluster/node"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/util/wait"
	"sigs.k8s.io/cluster-api/util"
	ctrl "sigs.k8s.io/controller-runtime"

	bkev1beta1 "gopkg.openfuyao.cn/cluster-api-provider-bke/api/v1beta1"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/kube"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/mergecluster"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/phaseframe"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/clusterutil"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/condition"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/constant"
)

const (
	EnsureClusterAPIObjName confv1beta1.BKEClusterPhase = "EnsureClusterAPIObj"
)

type EnsureClusterAPIObj struct {
	phaseframe.BasePhase
}

func NewEnsureClusterAPIObj(ctx *phaseframe.PhaseContext) phaseframe.Phase {
	base := phaseframe.NewBasePhase(ctx, EnsureClusterAPIObjName)
	return &EnsureClusterAPIObj{base}
}

func (e *EnsureClusterAPIObj) Execute() (_ ctrl.Result, err error) {
	if e.Ctx.BKECluster.OwnerReferences == nil {
		if err = e.reconcileCreateClusterAPIObj(); err != nil {
			return ctrl.Result{}, err
		}
	}

	ctx, cancel := context.WithTimeout(e.Ctx.Context, 5*time.Minute)
	defer cancel()
	err = wait.PollImmediateUntil(2*time.Second, func() (bool, error) {
		if err = e.reconcileClusterAPIObj(ctx); err != nil {
			return false, nil
		}
		return true, nil
	}, ctx.Done())

	if errors.Is(err, wait.ErrWaitTimeout) {
		return ctrl.Result{}, errors.Errorf("Wait master init failed")
	}

	if !clusterutil.FullyControlled(e.Ctx.BKECluster) {
		return ctrl.Result{Requeue: true}, nil
	}

	return ctrl.Result{}, nil
}

func (e *EnsureClusterAPIObj) NeedExecute(old *bkev1beta1.BKECluster, new *bkev1beta1.BKECluster) (needExecute bool) {
	if !e.BasePhase.NormalNeedExecute(old, new) {
		return false
	}
	if new.OwnerReferences != nil {
		return false
	}
	e.SetStatus(bkev1beta1.PhaseWaiting)
	return true
}

func (e *EnsureClusterAPIObj) reconcileCreateClusterAPIObj() error {
	ctx, c, bkeCluster, _, log := e.Ctx.Untie()

	defer func() {
		if err := mergecluster.SyncStatusUntilComplete(c, bkeCluster); err != nil {
			log.Error("failed to patch BKECluster,err: %s", err.Error())
			return
		}
	}()
	if _, ok := condition.HasCondition(bkev1beta1.ClusterAPIObjCondition, bkeCluster); ok {
		log.Info(constant.ClusterAPIObjNotReadyReason, "Waiting cluster api obj reconciled")
		return errors.New("Waiting cluster api obj reconciled")
	}
	log.Info(constant.ClusterAPIObjCreatingReason, "Start create cluster api obj")
	cfg, err := bkeinit.NewBkeConfigFromClusterConfig(bkeCluster.Spec.ClusterConfig)
	if err != nil {
		log.Error(constant.ReconcileErrorReason, "Failed to create bke config: %v", err)
		return errors.Errorf("Failed to create bke config: %v", err)
	}
	var externalEtcd map[string]string

	if clusterutil.IsBocloudCluster(bkeCluster) {
		externalEtcd = bkeinit.NewExternalEtcdConfig()
		//certGetter := certs.NewBKEKubernetesCertGetter(ctx, c, bkeCluster)
		//etcdCaCertContent, err := certGetter.GetCertContent(pkiutil.BKECertEtcdCA())
		//if err != nil {
		//	return err
		//}
		//etcdClientCertContent, err := certGetter.GetCertContent(pkiutil.BKECertEtcdAPIClient())
		//if err != nil {
		//	return err
		//}
		externalEtcd["etcdCAFile"] = "fakeCaCert"
		externalEtcd["etcdCertFile"] = "fakeCertFile"
		externalEtcd["etcdKeyFile"] = "fakeKeyFile"
		bkeNodes := bkenode.Nodes(bkeCluster.Spec.ClusterConfig.Nodes)
		etcdEndpointsLi := []string{}
		for _, node := range bkeNodes.Etcd() {
			tmp := fmt.Sprintf("https://%s:2379", node.IP)
			etcdEndpointsLi = append(etcdEndpointsLi, tmp)
		}
		externalEtcd["etcdEndpoints"] = strings.Join(etcdEndpointsLi, ",")
	}

	// generate cluster api obj yaml
	yamlPath, err := cfg.GenerateClusterAPIConfigFIle(bkeCluster.Name, bkeCluster.Namespace, externalEtcd)
	if err != nil {
		log.Error(constant.ClusterAPIObjNotReadyReason, "Failed to generate cluster api config file: %v", err)
		return err
	}
	localClient, err := kube.NewClientFromRestConfig(ctx, e.Ctx.RestConfig)
	if err != nil {
		log.Error(constant.ClusterAPIObjNotReadyReason, "Failed to create kube client: %v", err)
		return err
	}

	task := kube.NewTask("cluster-api", yamlPath, nil).
		SetOperate(bkeaddon.CreateAddon).
		SetWaiter(true, bkeinit.DefaultAddonTimeout, bkeinit.DefaultAddonInterval)
	// apply cluster api obj yaml
	if err := localClient.ApplyYaml(task); err != nil {
		log.Error(constant.ClusterAPIObjNotReadyReason, "Failed to create cluster api obj: %v", err)
		return err
	}
	log.Info(constant.ClusterAPIObjNotReadyReason, "Cluster api obj create success")
	condition.ConditionMark(bkeCluster, bkev1beta1.ClusterAPIObjCondition, confv1beta1.ConditionFalse, constant.ClusterAPIObjNotReadyReason, "cluster api obj create success")
	return nil
}

func (e *EnsureClusterAPIObj) reconcileClusterAPIObj(ctx context.Context) error {
	_, c, bkeCluster, _, log := e.Ctx.Untie()
	bkeCluster, err := mergecluster.GetCombinedBKECluster(ctx, e.Ctx.Client, bkeCluster.Namespace, bkeCluster.Name)
	if err != nil {
		return err
	}
	e.Ctx.BKECluster = bkeCluster

	if condition.HasConditionStatus(bkev1beta1.ClusterAPIObjCondition, bkeCluster, confv1beta1.ConditionFalse) {
		condition.ConditionMark(bkeCluster, bkev1beta1.ClusterAPIObjCondition, confv1beta1.ConditionTrue, constant.ClusterAPIObjReadyReason, "cluster api obj ready")
		if err := mergecluster.SyncStatusUntilComplete(c, bkeCluster); err != nil {
			log.Warn(constant.ReconcileErrorReason, "failed to update bkeCluster Status: %v", err)
			return errors.New("failed to update bkeCluster Status")
		}
	}
	if e.Ctx.BKECluster.OwnerReferences != nil {
		cluster, err := util.GetOwnerCluster(ctx, c, bkeCluster.ObjectMeta)
		if err != nil {
			if apierrors.IsNotFound(err) {
				return errors.New("Cluster Obj not found")
			}
			return errors.Errorf("Failed to get owner cluster: %v", err)
		}
		if cluster == nil {
			return errors.New("Cluster Controller has not yet set OwnerRef")
		}

		e.Ctx.Cluster = cluster
	}
	return nil
}
